﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;

namespace Quail
{
    /// <summary>
    /// Allows the ability to pre-create a handful of named test shells for use.  Any left-over test shells are (supposedly) rolled back.
    /// </summary>
    /// <typeparam name="T">A type of ITestShekk</typeparam>
    public class MultiShellQuickStarter<T> : IDisposable where T : ITestShell
    {
        private readonly Dictionary<string, T> _workingShells = new Dictionary<string, T>();
        private readonly ManualResetEvent _stopWorkingShells = new ManualResetEvent(false);
        private readonly List<string> _testNames;
        private readonly Func<string, T> _factory;
        private Thread _workerThread;


        /// <summary>
        /// Default constructor
        /// </summary>
        /// <param name="testNames">A list of test names</param>
        /// <param name="factory">A factory to create a test shell (the name of the test is the string parameter)</param>
        public MultiShellQuickStarter(IEnumerable<string> testNames, Func<string, T> factory)
        {
            _testNames = testNames.ToList();
            _factory = factory;
        }

        ~MultiShellQuickStarter()
        {
            Dispose(false);
        }

        /// <summary>
        /// Starts up the process
        /// </summary>
        /// <param name="task"></param>
        public void Start(Task task)
        {
            _stopWorkingShells.Reset();
            _workerThread = new Thread(PopulateWorkingShells);
            _workerThread.Start(task);
        }

        /// <summary>
        /// Call to free the leftover test shells
        /// </summary>
        public void Dispose()
        {
            Dispose(true);
            GC.SuppressFinalize(this);
        }

        protected virtual void Dispose(bool disposing)
        {
            if (disposing)
            {

                // kick off a thread to create each test method's test shell
                _stopWorkingShells.Set();

                if (_workerThread != null)
                    _workerThread.Join();

                lock (_workingShells)
                {
                    foreach (var shell in _workingShells.Values)
                    {
                        var t = (Task) shell.InstanceState;
                        t.Rollback();

                        shell.Dispose();
                    }
                    _workingShells.Clear();
                }
            }
        }

        private void PopulateWorkingShells(object thetask)
        {
            var task = (Task)thetask;

            foreach (var methodname in _testNames)
            {
                if (_stopWorkingShells.WaitOne(0))
                    break;

                // wait 1 second
                Thread.Sleep(1000);

                // var sttask = task.BeginTask(methodname);
                var shell = _factory(methodname);
                try
                {
                    // shell.InstanceState = sttask;
                    shell.Start(task);
                    shell.InstanceState = shell.ActiveTask;
                    lock (_workingShells)
                        _workingShells.Add(methodname, shell);
                }
                catch (Exception e)
                {
                    shell.Dispose();
                }
            }
        }

        public T GetShellAndTask(string testname, out Task task)
        {
            T shell;
            var lastTry = _workerThread.Join(0);
            for (; ; )
            {
                lock (_workingShells)
                {
                    if (_workingShells.TryGetValue(testname, out shell))
                    {
                        _workingShells.Remove(testname);
                        break;
                    }
                }

                if (lastTry)
                    throw new Exception("Test is asking for a shell [" + testname + "] that was not created - unable to continue");

                lastTry = _workerThread.Join(1000);
            }

            task = ((Task)shell.InstanceState).BeginTask(shell.TestName);
            shell.InstanceState = null;
            return shell;
        }
    }
}
